/**
 * 
 */
package edu.umd.clip.lm.model.training;

import java.util.*;

import com.sleepycat.bind.tuple.*;
import com.sleepycat.je.*;

/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
public class BDBActiveNodeStorage<T> extends ActiveNodeStorage<T> {
	private final Environment env;
	private Map<ActiveNodeCollection,Database> dbs;
	public static int MAX_LOG_FILE_SIZE = 100*1024*1024;
	
	public BDBActiveNodeStorage(Environment env) throws DatabaseException {
		this.env = env;
		dbs = new HashMap<ActiveNodeCollection, Database>();
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#close()
	 */
	@Override
	public boolean close() {
		try {
			env.close();
			return true;
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#open()
	 */
	@Override
	public boolean open() {
		return true;
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#createCollection(java.lang.String)
	 */
	@Override
	public ActiveNodeCollection createCollection(String name) {
		
		DatabaseConfig dbConf = new DatabaseConfig();
		dbConf.setReadOnly(false);
		dbConf.setAllowCreate(true);
		dbConf.setTransactional(false);
		dbConf.setDeferredWrite(true);
		
		try {
			env.truncateDatabase(null, name, false);
		} catch(DatabaseException e) {
		}
		
		Database db;
		try {
			db = env.openDatabase(null, name, dbConf);
		} catch (DatabaseException e) {
			e.printStackTrace();
			return null;
		}
		ActiveNodeCollection collection = new ActiveNodeCollection(name);
		dbs.put(collection, db);

		return collection;
	}

	private static class ActiveNodeBinding<T> extends TupleBinding<T> {
		private static final ActiveNodeBinding instance = new ActiveNodeBinding();
		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#entryToObject(com.sleepycat.bind.tuple.TupleInput)
		 */
		@Override
		public T entryToObject(TupleInput input) {
			int size = input.readInt();
			byte bytes[] = new byte[size];
			int read = 0;
			while(read < size) {
				int now = input.readFast(bytes, read, size-read);
				if (now <= 0) break;
				read += now;
			}
			return deserializeActiveNode(bytes);
		}

		/* (non-Javadoc)
		 * @see com.sleepycat.bind.tuple.TupleBinding#objectToEntry(java.lang.Object, com.sleepycat.bind.tuple.TupleOutput)
		 */
		@Override
		public void objectToEntry(T object, TupleOutput output) {
			byte bytes[] = serializeActiveNode(object);
			output.writeInt(bytes.length);
			output.writeFast(bytes);
		}
	}
	
	private static final IntegerBinding keyBinding = new IntegerBinding();
	
	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#getActiveNode(edu.umd.clip.lm.model.training.ActiveNodeStorage.ActiveNodeCollection, int)
	 */
	@Override
	protected T getActiveNode(ActiveNodeCollection collection, int id) {
		Database db = dbs.get(collection);
		if (db == null) throw new Error("database not found "+collection.name);
		
		DatabaseEntry key = new DatabaseEntry();
		keyBinding.objectToEntry(id, key);

		DatabaseEntry data = new DatabaseEntry();

		try {
			OperationStatus status = db.get(null, key, data, null);
			if (status == OperationStatus.SUCCESS) {
				T node = (T) ActiveNodeBinding.instance.entryToObject(data);
				return node;
			} else if (status == OperationStatus.NOTFOUND) {
				System.err.printf("active node #%d not found in %s\n", id, collection.name);
			}
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		
		return null;
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#getCollection(java.lang.String)
	 */
	@Override
	public ActiveNodeCollection getCollection(String name) {
		ActiveNodeCollection collection = new ActiveNodeCollection(name);
		if (!dbs.containsKey(collection)) {
			DatabaseConfig dbConf = new DatabaseConfig();
			dbConf.setReadOnly(false);
			dbConf.setTransactional(false);
			
			Database db;
			try {
				db = env.openDatabase(null, name, dbConf);
			} catch (DatabaseNotFoundException e) {
				return null;
			} catch (DatabaseException e) {
				e.printStackTrace();
				return null;
			}
			dbs.put(collection, db);
		}
		return collection;
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#putActiveNode(edu.umd.clip.lm.model.training.ActiveNodeStorage.ActiveNodeCollection, int, edu.umd.clip.lm.model.training.ActiveTreeNode)
	 */
	@Override
	protected boolean putActiveNode(ActiveNodeCollection collection, int id,	T node) {
		Database db = dbs.get(collection);
		if (db != null) {
			DatabaseEntry key = new DatabaseEntry();
			keyBinding.objectToEntry(id, key);

			DatabaseEntry data = new DatabaseEntry();
			ActiveNodeBinding.instance.objectToEntry(node, data);
			try {
				OperationStatus status = db.put(null, key, data);
				if (status == OperationStatus.SUCCESS) return true;
			} catch (DatabaseException e) {
				e.printStackTrace();
			}
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see edu.umd.clip.lm.model.training.ActiveNodeStorage#removeCollection(java.lang.String)
	 */
	@Override
	public boolean removeCollection(String name) {
		Database db = dbs.remove(new ActiveNodeCollection(name));
		try {
			if (db != null) db.close();
			env.removeDatabase(null, name);
			return true;
		} catch(DatabaseNotFoundException e) {
			
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return false;
	}

	public Environment getEnv() {
		return env;
	}

}
